/**
 * 判断是不是同一个元素
 * 
 * @param {*} n1 旧节点
 * @param {*} n2 新节点
 */
function isSameVnode(n1, n2) {
	return n1.tag == n2.tag && n1.key === n2.key;
}

/**
 * 处理标签上的属性
 * 
 * @param {Object} vnode  虚拟 DOM
 * @param {Object} oldProps 旧的属性
 */
function updateProperties(vnode, oldProps = {}) {
	const el = vnode.el,
		newProps = vnode.data || {};

	// style属性中的 diff 比对
	const oldStyle = oldProps.style || {};
	const newStyle = newProps.style || {};
	for (const key in oldStyle) {
		// 旧的有样式，新的没有样式，删除掉老的样式
		if (!(key in newStyle)) {
			el.style[key] = '';
		}
	}

	// 属性的 diff 比对
	for (const key in oldProps) {
		// 旧的有属性，新的没有属性，删除掉老的属性
		if (!(key in newProps)) {
			el.removeAttribute(key);
		}
	}

	// 设置属性到标签上
	for (const key in newProps) {
		if (key === "style") {
			for (const sKey in newProps.style) {
				el.style[sKey] = newProps.style[sKey];
			}
		} else if (key === "class") {
			// el.className = newProps.class;
			el.classList.add(newProps.class);
		} else {
			el.setAttribute(key, newProps[key]);
		}
	}
}

/**
 * 将虚拟 DOM 转换成真实 DOM
 * @param {*} vnode 虚拟节点
 */
export function createEle(vnode) {
	const { tag, children, data: props, key, text } = vnode;

	// 如果是字符串
	if (typeof tag === "string") {
		// tag 如果是组件，需要判断处理
		// console.log("标签是", vnode.tag, "属性是", vnode.data);
		// 如果是标签
		// 存放一份真实的 DOM，后续 diff 使用
		vnode.el = document.createElement(tag); // 后面需要 diff 算法，获取虚拟节点对比后更新 DOM
		updateProperties(vnode); // 处理标签上的属性
		children &&
			children.forEach((child) => {
				// 递归处理子元素
				return vnode.el.appendChild(createEle(child));
			});
	} else {
		// 如果是文本
		// 存放一份真实的DOM，后续使用
		vnode.el = document.createTextNode(text);
	}
	return vnode.el;
}

/**
 * 通过虚拟节点（vnode）创建出真实节点
 *
 * @param {*} oldVnode 需要挂载的容器（更新的时候可能是一个旧的虚拟节点）
 * @param {*} vnode 新虚拟节点
 */
export function patch(oldVnode, vnode) {
	// console.log("patch", vnode);
	const isRealElement = oldVnode.nodeType; // 是一个 DOM 元素
	if (isRealElement) {
		const oldEle = oldVnode;
		// 获取父元素，将当前元素节点的下一个元素节点作为参照物，将它插入到老元素节点的前面即可，之后删除旧的元素节点
		const parentNode = oldEle.parentNode; // 父元素节点
		const el = createEle(vnode); // 根据虚拟节点生成真实 DOM 节点, 返回真实的 DOM 元素节点
		parentNode.insertBefore(el, oldEle.nextSibling);
		parentNode.removeChild(oldEle);
		return el;
	} else {
		// 更新时候，diff 算法
		return patchVnode(oldVnode, vnode);
	}
}
/**
 * 开始比对节点
 * @param {*} oldVnode 
 * @param {*} vnode 
 * @returns 
 */
function patchVnode(oldVnode, vnode) {
	// case1: 新旧节点不是同一个元素节点，直接替换点老节点
	if (!isSameVnode(oldVnode, vnode)) { // 不是同一个元素节点
		const newNode = createEle(vnode);
		oldVnode.el.parentNode.replaceChild(newNode, oldVnode.el);
		return newNode; // 返回新节点
	} else {
		// 是同一个元素节点
		//  节点复用
		const el = vnode.el = oldVnode.el;

		// case2：新旧节点都是文本，并且新旧文本不一样，用新文本直接替换掉老节点
		if (!oldVnode.tag) { // 文本
			// 新旧文本不一样
			if (oldVnode.text !== vnode.text) {
				el.textContent = vnode.text;
				return vnode; // 返回新节点
			}
		}

		// case3：新旧节点都是标签，还需要比较属性
		updateProperties(vnode, oldVnode.data);

		// case4：比对子元素，两种情况，一：都有子元素;二：一方有子元素一方没有子元素。
		const oldChildren = oldVnode.children || [];
		const newChildren = vnode.children || [];

		if (newChildren.length && !oldChildren.length) {
			// 新的有子元素，旧的没有子元素
			// console.log('新的有子元素，旧的没有子元素')
			for (let i = 0; i < newChildren.length; i++) {
				const newEl = createEle(newChildren[i]);
				el.appendChild(newEl);
				return newEl;  // 返回新节点
			}
		} else if (!newChildren.length && oldChildren.length) {
			// 新的没有子元素，旧的有子元素
			// console.log('新的没有子元素，旧的有子元素')
			// el.innerHTML = "";
			el.parentNode.removeChild(el); // 干掉老的
			return null;
		} else {
			// 新旧都有子元素 oldChildren.length && newChildren.length 都大于 0
			// 真正的 diff
			// console.log('真正的复杂diff')
			return updateChildren(el, oldChildren, newChildren);
		}
	}
}

/**
 * diff 比对，采用了双指针的方式来比对 O(n)
 * 
 * @param {*} el 
 * @param {*} oldChildren 
 * @param {*} newChildren 
 */
function updateChildren(el, oldChildren, newChildren) {
	let oldStartIndex = 0;
	let oldEndIndex = oldChildren.length - 1;
	let newStartIndex = 0;
	let newEndIndex = newChildren.length - 1;

	let oldStartVnode = oldChildren[0];
	let oldEndVnode = oldChildren[oldEndIndex];
	let newStartVnode = newChildren[0];
	let newEndVnode = newChildren[newEndIndex];

	// 无论那一方比对完都停止
	while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
		if (isSameVnode(oldStartVnode, newStartVnode)) {
			// 新旧是同一个元素(头头开始比较，从前往后)
			patchVnode(oldStartVnode, newStartVnode);
			oldStartVnode = oldChildren[++oldStartIndex];
			newStartVnode = newChildren[++newStartIndex];
		} else if (isSameVnode(oldEndVnode, newEndVnode)) {
			// 新旧是同一个元素(尾尾开始比较，从后往前)
			patchVnode(oldEndVnode, newEndVnode);
			oldEndVnode = oldChildren[--oldEndIndex];
			newEndVnode = newChildren[--newEndIndex];
		}
	}
	if (newStartIndex <= newEndIndex) {
		for (let i = newStartIndex; i <= newEndIndex; i++) {
			// 可能从前面插入，也可能是从后面追加
			const nextVnode = newChildren[newEndIndex + 1] == null ? null : newChildren[newEndIndex + 1].el;
			// if (nextVnode) {
			// 	el.insertBefore(createEle(newChildren[i]), nextVnode);
			// } else {
			// 	el.appendChild(createEle(newChildren[i]));
			// }
			// 由于insertBefor很智能，能够判断nextVnode是否为null，来向前插入还是向后追加，所以不用判断了
			const newEl = createEle(newChildren[i]);
			el.insertBefore(newEl, nextVnode);
			return newEl;
		}
	}
}
